/*
 * xlogrecord.h
 *
 * WAL 记录格式的定义。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/access/xlogrecord.h
 */
#ifndef XLOGRECORD_H
#define XLOGRECORD_H

#include "access/rmgr.h"
#include "access/xlogdefs.h"
#include "port/pg_crc32c.h"
#include "storage/block.h"
#include "storage/relfilenode.h"

/*
 * XLOG记录的总体布局为：
 *		固定大小的头部（XLogRecord结构）
 *		XLogRecordBlockHeader结构
 *		XLogRecordBlockHeader结构
 *		...
 *		XLogRecordDataHeader[Short|Long]结构
 *		block数据
 *		block数据
 *		...
 *		主要数据
 *
 * 可以有零个或多个XLogRecordBlockHeaders，以及零个或多个与块无关的rmgr特定数据字节。
 * XLogRecord结构始终在WAL文件的MAXALIGN边界上开始，但其他字段不对齐。
 *
 * XLogRecordBlockHeader、XLogRecordDataHeaderShort和
 * XLogRecordDataHeaderLong结构都以单个'id'字节开头。它用于区分块引用和主要数据结构。
 */
typedef struct XLogRecord
{
	uint32		xl_tot_len;		/* 整个记录的总长度 */
	TransactionId xl_xid;		/* xact id */
	XLogRecPtr	xl_prev;		/* 日志中前一记录的指针 */
	uint8		xl_info;		/* 标志位，见下文 */
	RmgrId		xl_rmid;		/* 此记录的资源管理器 */
	/* 这里的2字节填充，初始化为零 */
	pg_crc32c	xl_crc;			/* 此记录的CRC */

	/* 随后是XLogRecordBlockHeaders和XLogRecordDataHeader，没有填充 */

} XLogRecord;

#define SizeOfXLogRecord	(offsetof(XLogRecord, xl_crc) + sizeof(pg_crc32c))

/*
 * xl_info中的高4位可以由rmgr自由使用。
 * XLR_SPECIAL_REL_UPDATE和XLR_CHECK_CONSISTENCY位可以由XLogInsert的调用者传递。
 * 其余部分由XLogInsert内部设置。
 */
#define XLR_INFO_MASK			0x0F
#define XLR_RMGR_INFO_MASK		0xF0

/*
 * 如果WAL记录以未覆盖的方式修改任何关系文件，则设置此标志。
 * PostgreSQL本身不使用此标志，但它允许读取WAL并跟踪修改块的外部工具识别这些特殊记录类型。
 */
#define XLR_SPECIAL_REL_UPDATE	0x01

/*
 * 强制在恢复时检查重放WAL的一致性。如果启用，
 * 每个记录将为记录修改的每个块记录一个完整页面写入，
 * 并随后在一致性检查中重用它。XLogInsert的调用者
 * 如果必要，可以使用此值，但如果为rmgr启用wal_consistency_checking，则无条件设置此值。
 */
#define XLR_CHECK_CONSISTENCY	0x02

/*
 * 附加到XLOG记录的块数据的头部信息。
 *
 * 'data_length'是与该块关联的rmgr特定有效载荷数据的长度。
 * 它不包括可能的完整页面图像，也不包括XLogRecordBlockHeader结构本身。
 *
 * 请注意，我们不尝试对齐XLogRecordBlockHeader结构！
 * 因此，结构在使用之前必须复制到对齐的本地存储中。
 */
typedef struct XLogRecordBlockHeader
{
	uint8		id;				/* 块引用 ID */
	uint8		fork_flags;		/* 关系内的 fork，以及标志 */
	uint16		data_length;	/* 载荷字节数（不包括页面图像） */

	/* 如果 BKPBLOCK_HAS_IMAGE，接下来是一个 XLogRecordBlockImageHeader 结构 */
	/* 如果未设置 BKPBLOCK_SAME_REL，则接下来是一个 RelFileNode */
	/* 块编号接下来是 */
} XLogRecordBlockHeader;

#define SizeOfXLogRecordBlockHeader (offsetof(XLogRecordBlockHeader, data_length) + sizeof(uint16))

/*
 * 包含完整页面图像时的附加头信息
 * （即，当设置 BKPBLOCK_HAS_IMAGE 时）。
 *
 * XLOG 代码意识到 PG 数据页面通常在中间包含一个未使用的“孔”，
 * 其中仅包含零字节。由于我们知道这个“孔”全是零，
 * 我们将其从存储的数据中移除（并且它也不计入 XLOG 记录的 CRC）。
 * 因此，实际存在的块数据量为 (BLCKSZ - <“孔”字节的长度>)。
 *
 * 此外，当 wal_compression 被启用时，我们将在移除
 * “孔”后尝试使用支持的算法之一来压缩完整页面图像。
 * 这可以减少 WAL 量，但在 WAL 记录期间会消耗一些额外的 CPU 进行压缩。
 * 在这种情况下，由于无法通过从 BLCKSZ 中减去页面图像字节数来计算
 * “孔”长度，因此基本上需要将其存储为额外信息。
 * 但当不存在“孔”时，我们可以假设“孔”长度为零，
 * 无需存储这样的额外信息。请注意，如果
 * 通过压缩节省的字节数小于额外信息的长度，则 WAL 中存储的是
 * 页面图像的原始版本，而不是压缩版本。因此，当页面图像成功
 * 压缩时，实际存在的块数据量小于
 * BLCKSZ - “孔”字节的长度 - 额外信息的长度。
 */
typedef struct XLogRecordBlockImageHeader
{
	uint16		length;			/* 页面图像字节数 */
	uint16		hole_offset;	/* “孔”之前的字节数 */
	uint8		bimg_info;		/* 标志位，见下文 */

	/*
	 * 如果 BKPIMAGE_HAS_HOLE 和 BKPIMAGE_COMPRESSED()，接下来是
	 * 一个 XLogRecordBlockCompressHeader 结构。
	 */
} XLogRecordBlockImageHeader;

#define SizeOfXLogRecordBlockImageHeader	\
	(offsetof(XLogRecordBlockImageHeader, bimg_info) + sizeof(uint8))

/* 存储在 bimg_info 中的信息 */
#define BKPIMAGE_HAS_HOLE		0x01	/* 页面图像有“孔” */
#define BKPIMAGE_APPLY			0x02	/* 页面图像应在重放期间恢复 */
/* 支持的压缩方法 */
#define BKPIMAGE_COMPRESS_PGLZ	0x04
#define BKPIMAGE_COMPRESS_LZ4	0x08
#define BKPIMAGE_COMPRESS_ZSTD	0x10

#define	BKPIMAGE_COMPRESSED(info) \
	((info & (BKPIMAGE_COMPRESS_PGLZ | BKPIMAGE_COMPRESS_LZ4 | \
			  BKPIMAGE_COMPRESS_ZSTD)) != 0)

/*
 * 当页面图像有“孔”并且被压缩时使用的额外头信息。
 */
typedef struct XLogRecordBlockCompressHeader
{
	uint16		hole_length;	/* “孔”的字节数 */
} XLogRecordBlockCompressHeader;

#define SizeOfXLogRecordBlockCompressHeader \
	sizeof(XLogRecordBlockCompressHeader)

/*
 * 块引用的头最大大小。这用于为构造头部
 * 分配临时缓冲区。
 */
#define MaxSizeOfXLogRecordBlockHeader \
	(SizeOfXLogRecordBlockHeader + \
	 SizeOfXLogRecordBlockImageHeader + \
	 SizeOfXLogRecordBlockCompressHeader + \
	 sizeof(RelFileNode) + \
	 sizeof(BlockNumber))

/*
 * fork 编号适合在 fork_flags 字段的低 4 位。高位
 * 用于标志。
 */
#define BKPBLOCK_FORK_MASK	0x0F
#define BKPBLOCK_FLAG_MASK	0xF0
#define BKPBLOCK_HAS_IMAGE	0x10	/* 块数据是一个 XLogRecordBlockImage */
#define BKPBLOCK_HAS_DATA	0x20
#define BKPBLOCK_WILL_INIT	0x40	/* redo 将重新初始化页面 */
#define BKPBLOCK_SAME_REL	0x80	/* RelFileNode 被省略，与之前相同 */

/*
 * XLogRecordDataHeaderShort/Long 用于记录的“主数据”部分。
 * 如果数据的长度小于 256 字节，则使用短格式，
 * 用一个字节来保存长度。否则使用长格式。
 *
 * （这些结构当前在代码中未使用，仅用于
 * 文档目的）。
 */
typedef struct XLogRecordDataHeaderShort
{
	uint8		id;				/* XLR_BLOCK_ID_DATA_SHORT */
	uint8		data_length;	/* 有效负载字节数 */
}			XLogRecordDataHeaderShort;

#define SizeOfXLogRecordDataHeaderShort (sizeof(uint8) * 2)

typedef struct XLogRecordDataHeaderLong
{
	uint8		id;				/* XLR_BLOCK_ID_DATA_LONG */
	/* 后跟 uint32 data_length，未对齐 */
}			XLogRecordDataHeaderLong;

#define SizeOfXLogRecordDataHeaderLong (sizeof(uint8) + sizeof(uint32))

/*
 * 用于区分不同类型记录碎片的块 ID。块
 * 引用编号从 0 到 XLR_MAX_BLOCK_ID。rmgr 可以自由使用
 * 该范围内的任何 ID 号码（尽管你应该坚持使用小号码，
 * 因为 WAL 机制是针对这种情况进行优化的）。一些 ID
 * 号码被保留用于表示记录的“主要”数据部分，
 * 以及支持复制的事务元数据。
 *
 * 当前最大值设定为 32，完全是任意的。大多数记录只
 * 需要少量块引用，但也有少数例外需要更多。
 */
#define XLR_MAX_BLOCK_ID			32

#define XLR_BLOCK_ID_DATA_SHORT		255
#define XLR_BLOCK_ID_DATA_LONG		254
#define XLR_BLOCK_ID_ORIGIN			253
#define XLR_BLOCK_ID_TOPLEVEL_XID	252

#endif							/* XLOGRECORD_H */
